﻿using System;
using System.IO;
using System.Text;
using System.Threading;
using Netbits.Common.Collections;
using Netbits.Common.Conditions;
using Netbits.Common.Threading;
using Netbits.Common.Extensions;

namespace Netbits.Common.Databases.MemoryTree
{
	/// <summary>
	/// Represent a "cluster" collection of all instanced memory-tree-db items.
	/// </summary>
	public sealed class MemoryTreeCluster : IDisposable
	{
		#region Private Variables

		ThreadEntry _SaveThread;
		MemoryTreeDb _Database;
		MemoryTreeNode _DatabaseElement;
		Encoding _Encoder = Encoding.UTF8;
		string _CryptPassword;
		LockedKeyedCollection<Guid, MemoryTreeDb> _Databases;
		string _DatabasePath;
		bool _Disposed;

		#endregion

		#region Constructor

		/// <summary>
		/// Create a new memory-tree-cluster with specified Database-Path.
		/// </summary>
		/// <param name="DatabasePath_">Path for all database-files.</param>
		public MemoryTreeCluster( string DatabasePath_ )
		{
			_DatabasePath = DatabasePath_;
			_Databases = new LockedKeyedCollection<Guid,MemoryTreeDb>( item => item.Id );
		}

		#endregion

		#region Public Properties

		/// <summary>
		/// Get or set the crypt-password.
		/// If string.empty is set no encryption is active.
		/// </summary>
		public string CryptPassword
		{
			get
			{
				return _CryptPassword;
			}
			set
			{
				_CryptPassword = value;
			}
		}

		/// <summary>
		/// Get or set the Encoding system 
		/// Default is UTF8
		/// </summary>
		public Encoding Encoder
		{
			get
			{
				return _Encoder;
			}
			set
			{
				_Encoder = value;
			}
		}

		/// <summary>
		/// Return the database-path.
		/// </summary>
		public string DatabasePath
		{
			get
			{
				return _DatabasePath;
			}
		}

		#endregion

		#region Public Functions

		/// <summary>
		/// Add new database to the cluster
		/// </summary>
		/// <param name="Name_">Name of the new database</param>
		/// <returns>Return the new memory-tree-db otherwise null</returns>
		public MemoryTreeDb Add( string Name_ )
		{
			if( string.IsNullOrEmpty( Name_ ) )
				throw new ArgumentNullException( "Name_" );

			if( Find( Name_ ) != null )
				throw new Exception( "memory-tree-db exists!" );

			string filename = FileExtensions.Combine( _DatabasePath, Name_ + ".sdb" );
			if( File.Exists( filename ) )
				File.Delete( filename );

			MemoryTreeDb mtb = new MemoryTreeDb( filename );
			mtb.Load();
			mtb.Name = Name_;

			MemoryTreeNode mtbNode = _DatabaseElement.Add();
			mtbNode.SetField( "filename", filename );

			_Databases.Add( mtb );
			return mtb;
		}

		/// <summary>
		/// Remove the memory-tree-db with specified id 
		/// </summary>
		/// <param name="Id_">Id of the memory-tree-db</param>
		public void Remove( Guid Id_ )
		{
			if( Id_ == Guid.Empty )
				throw new ArgumentNullException( "Id_" );

			Remove( Find( Id_ ) );
		}

		/// <summary>
		/// Remove the memory-tree-db with specified name
		/// </summary>
		/// <param name="Name_">Name of the memory-tree-db</param>
		public void Remove( string Name_ )
		{
			if( string.IsNullOrEmpty( Name_ ) )
				throw new ArgumentNullException( "Name_" );

			Remove( Find( Name_ ) );
		}

		/// <summary>
		/// Remove the memory-tree-db with specified database-instance.
		/// </summary>
		/// <param name="Database_">Instance of the database to remove</param>
		public void Remove( MemoryTreeDb Database_ )
		{
			Condition.Requires( Database_ ).IsNotNull();

			MemoryTreeNode mtn = _DatabaseElement.FindNode( "id", Database_.Id );
			if( mtn == null )
				throw new Exception( "database not found" );

			mtn.Remove();
			_Databases.Remove( Database_ );

			Database_.Dispose();
			if( File.Exists( Database_.DatabaseFilename ) )
				File.Delete( Database_.DatabaseFilename );
		}

		/// <summary>
		/// Find the memory-tree-db with specified name
		/// </summary>
		/// <param name="Name_">The name of the database to search</param>
		/// <returns>The found memory-tree-db otherwise null</returns>
		public MemoryTreeDb Find( string Name_ )
		{
			if( string.IsNullOrEmpty( Name_ ) )
				throw new ArgumentNullException( "Name_" );

			foreach( MemoryTreeDb db in _Databases )
			{
				if( string.CompareOrdinal( db.Name, Name_ ) == 0 )
					return db;
			}

			return null;
		}

		/// <summary>
		/// Find the memory-tree-db with specified id
		/// </summary>
		/// <param name="Id_">The id of the database to search</param>
		/// <returns>The found memory-tree-db otherwise null</returns>
		public MemoryTreeDb Find( Guid Id_ )
		{
			return _Databases[Id_];
		}

		/// <summary>
		/// Load all databases specified in this cluster. 
		/// </summary>
		public void Load()
		{
			if( _Database != null )
				return;

			if( !Directory.Exists( _DatabasePath ) )
				Directory.CreateDirectory( _DatabasePath );

			_Database = new MemoryTreeDb( FileExtensions.Combine( _DatabasePath, "master.sdb" ) );
			_Database.EncryptionPassword = _CryptPassword;
			_Database.Encoder = _Encoder;
			_Database.Load();

			_DatabaseElement = _Database.Root.FindNode( "databases" );
			if( _DatabaseElement == null )
				_DatabaseElement = _Database.Root.Add( "databases" );

			foreach( MemoryTreeNode xde in _DatabaseElement )
			{
				string filename = xde.GetField( "filename", string.Empty );
				if( string.IsNullOrEmpty( filename ) )
					continue;

				MemoryTreeDb db = new MemoryTreeDb( filename );
				db.EncryptionPassword = _CryptPassword;
				db.Encoder = _Encoder;
				db.Load();
				_Databases.Add( db );
			}

			if( _SaveThread == null )
			{
				_SaveThread = ThreadManager.Create( "mtc-save-handler" );
				_SaveThread.IsBackground = false;
				_SaveThread.Priority = ThreadPriority.AboveNormal;
				_SaveThread.SleepAfterRun = new TimeSpan( 500 );
				_SaveThread.Run = _SaveHandler;
				_SaveThread.Start();
			}
		}

		/// <summary>
		/// Close all databases in this cluster
		/// </summary>
		public void Unload()
		{
			Dispose();
		}

		#endregion

		#region Private Functions

		void _SaveHandler( ThreadEntry Thread_, object[] Params_ )
		{
			foreach( MemoryTreeDb db in _Databases )
				db.__Save();

			_Database.__Save();
		}

		#endregion

		#region IDisposable

		/// <summary>
		/// Dispose all databases in this cluster
		/// </summary>
		public void Dispose()
		{
			if( !_Disposed )
			{
				if( _Databases != null )
				{
					if( _SaveThread != null )
					{
						ThreadManager.Remove( _SaveThread );
						_SaveThread.Dispose();
						_SaveThread = null;
					}

					foreach( MemoryTreeDb mtd in _Databases )
						mtd.Dispose();
					_Databases.Clear();

					_DatabaseElement = null;
					if( _Database != null )
					{
						_Database.Dispose();
						_Database = null;
					}
				}

				_Disposed = true;
			}

			GC.SuppressFinalize( this );
		}

		#endregion
	}
}
